/* 
 *  Copyright (C) 2009-2010 milkyjing <milkyjing@gmail.com>
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "tcx.h"
#include "../libtcc/tcc.c"
#include "../libsyl/syl.c"
#include "../libtm/tm.c"
#include "../tcxpy/py.c"
#include "../../../../libtcs project/libtcs/trunk/tcs.c"


const wchar_t *tcx_get_root_directory(wchar_t **pDirectory) {
    int i, len, indicator, count;
    wchar_t szDirectory[MAX_PATH];
    wchar_t *directory;
    len = GetModuleFileNameW(NULL, szDirectory, MAX_PATH);
    for (i = len - 1; i >= 0; i --) {
        if (L'\\' == szDirectory[i] || L'/' == szDirectory[i]) {
            indicator = i;
            break;
        }
    }
    count = indicator;
    directory = (wchar_t *)malloc((count + 1) * sizeof(wchar_t));
    memcpy(directory, szDirectory, count * sizeof(wchar_t));
    directory[count] = L'\0';
    *pDirectory = directory;
    return (const wchar_t *)directory;
}

const wchar_t *tcx_get_tcc_directory(const wchar_t *tccFilename, wchar_t **pTccDirectory) {
    int i, len, count, indicator;
    wchar_t *fullTccFilename;
    wchar_t *tccDirectory;
    len = wcslen(tccFilename);
    count = __max(len, 4095) + 1;
    fullTccFilename = (wchar_t *)malloc(count * sizeof(wchar_t));
    if (wcsstr(tccFilename, L":") == NULL)
        GetFullPathNameW(tccFilename, count, fullTccFilename, NULL);
    else
        memcpy(fullTccFilename, tccFilename, (len + 1) * sizeof(wchar_t));
    len = wcslen(fullTccFilename);
    for (i = len - 1; i >= 0; i --) {
        if (L'\\' == fullTccFilename[i] || L'/' == fullTccFilename[i]) {
            indicator = i;
            break;
        }
    }
    count = indicator;
    tccDirectory = (wchar_t *)malloc((count + 1) * sizeof(wchar_t));
    memcpy(tccDirectory, fullTccFilename, count * sizeof(wchar_t));
    tccDirectory[count] = L'\0';
    free(fullTccFilename);
    *pTccDirectory = tccDirectory;
    return (const wchar_t *)tccDirectory;
}

const wchar_t *tcx_make_out_filename_from_tcc(const wchar_t *tccFilename, wchar_t **pOutFilename) {
    int count;
    wchar_t *outFilename;
    count = wcslen(tccFilename);
    if (_wcsicmp(tccFilename + count - 4, L".tcc") == 0) {
        count -= 4;
        outFilename = (wchar_t *)malloc((count + 1) * sizeof(wchar_t));
        memcpy(outFilename, tccFilename, count * sizeof(wchar_t));
        outFilename[count] = L'\0';
    } else {
        outFilename = (wchar_t *)malloc((count + 1) * sizeof(wchar_t));
        memcpy(outFilename, tccFilename, (count + 1) * sizeof(wchar_t));
    }
    *pOutFilename = outFilename;
    return (const wchar_t *)outFilename;
}

const wchar_t *tcx_get_out_ass_filename(const wchar_t *outFilename, wchar_t **pOutAssFilename) {
    int count;
    wchar_t *assFilename;
    count = wcslen(outFilename);
    assFilename = (wchar_t *)malloc((count + 5) * sizeof(wchar_t));
    memcpy(assFilename, outFilename, count * sizeof(wchar_t));
    memcpy(assFilename + count, L".ass\0", 5 * sizeof(wchar_t));
    *pOutAssFilename = assFilename;
    return (const wchar_t *)assFilename;
}

static void _tcx_sz_unicode_to_ansi(const wchar_t *uni, char **ansi) {
    int size;
    char *sz;
    size = WideCharToMultiByte(CP_ACP, 0, uni, -1, NULL, 0, NULL, NULL);
    sz = (char *)malloc(size * sizeof(char));
    WideCharToMultiByte(CP_ACP, 0, uni, -1, sz, size, NULL, NULL);
    *ansi = sz;
}

const char *tcx_get_out_tcs_filename(const wchar_t *outFilename, char **pOutTcsFilename) {
    int count;
    wchar_t *tcsFilename;
    char *ansiTcsFilename;
    count = wcslen(outFilename);
    tcsFilename = (wchar_t *)malloc((count + 5) * sizeof(wchar_t));
    memcpy(tcsFilename, outFilename, count * sizeof(wchar_t));
    memcpy(tcsFilename + count, L".tcs\0", 5 * sizeof(wchar_t));
    _tcx_sz_unicode_to_ansi(tcsFilename, &ansiTcsFilename);
    free(tcsFilename);
    *pOutTcsFilename = ansiTcsFilename;
    return (const char *)ansiTcsFilename;
}

static void _tcx_make_full_path_of_syl_file(const wchar_t *tccDirectory, TCC_pAttributes pAttributes) {
    if (L'\0' != pAttributes->k_timed_ass_file[0] && wcsstr(pAttributes->k_timed_ass_file, L":") == NULL) {
        int dir_len, len, count;
        wchar_t *tempString;
        dir_len = wcslen(tccDirectory);
        tempString = pAttributes->k_timed_ass_file;
        len = wcslen(tempString);
        count = dir_len + 1 + len + 1;
        pAttributes->k_timed_ass_file = (wchar_t *)malloc(count * sizeof(wchar_t));
        memcpy(pAttributes->k_timed_ass_file, tccDirectory, dir_len * sizeof(wchar_t));
        pAttributes->k_timed_ass_file[dir_len] = L'\\';
        memcpy(pAttributes->k_timed_ass_file + dir_len + 1, tempString, (len + 1) * sizeof(wchar_t));
        free(tempString);
    }
}

static void _tcx_make_full_path_of_font_file(const wchar_t *tccDirectory, TCC_pAttributes pAttributes) {
    if (wcsstr(pAttributes->font_file, L":") == NULL) {
        int dir_len, len, count;
        wchar_t *tempString;
        dir_len = wcslen(tccDirectory);
        tempString = pAttributes->font_file;
        len = wcslen(tempString);
        count = dir_len + 1 + len + 1;
        pAttributes->font_file = (wchar_t *)malloc(count * sizeof(wchar_t));
        memcpy(pAttributes->font_file, tccDirectory, dir_len * sizeof(wchar_t));
        pAttributes->font_file[dir_len] = L'\\';
        memcpy(pAttributes->font_file + dir_len + 1, tempString, (len + 1) * sizeof(wchar_t));
        if (GetFileAttributesW(pAttributes->font_file) == INVALID_FILE_ATTRIBUTES) {
            int lenWinDir;
            wchar_t szWinDir[MAX_PATH];
            free(pAttributes->font_file);
            GetWindowsDirectoryW(szWinDir, MAX_PATH);
            lenWinDir = wcslen(szWinDir);
            count = lenWinDir + 7 + len + 1;
            pAttributes->font_file = (wchar_t *)malloc(count * sizeof(wchar_t));
            memcpy(pAttributes->font_file, szWinDir, lenWinDir * sizeof(wchar_t));
            memcpy(pAttributes->font_file + lenWinDir, L"\\Fonts\\", 7 * sizeof(wchar_t));
            memcpy(pAttributes->font_file + lenWinDir + 7, tempString, (len + 1) * sizeof(wchar_t));
        }
        free(tempString);
    }
}

static void _tcx_make_full_path_of_py_file(const wchar_t *tccDirectory, TCC_pAttributes pAttributes) {
    int i, lenTccDir, lenRootDir, len, count;
    wchar_t *rootDirectory;
    wchar_t *tempString;
    wchar_t *str;
    tcx_get_root_directory(&rootDirectory);
    lenRootDir = wcslen(rootDirectory);
    lenTccDir = wcslen(tccDirectory);
    for (i = 0; i < pAttributes->py_file_count; i ++) {
        str = wcsstr(pAttributes->py_file[i], L"!\\");
        if (!str) str = wcsstr(pAttributes->py_file[i], L"!/");
        if (str) {
            len = wcslen(str);
            count = lenRootDir + 1 + len - 2 + 1;
            tempString = pAttributes->py_file[i];
            pAttributes->py_file[i] = (wchar_t *)malloc(count * sizeof(wchar_t));
            memcpy(pAttributes->py_file[i], rootDirectory, lenRootDir * sizeof(wchar_t));
            pAttributes->py_file[i][lenRootDir] = L'\\';
            memcpy(pAttributes->py_file[i] + lenRootDir + 1, str + 2, (len - 2 + 1) * sizeof(wchar_t));
            free(tempString);
        } else if (wcsstr(pAttributes->py_file[i], L":") == NULL) {
            tempString = pAttributes->py_file[i];
            len = wcslen(tempString);
            count = lenTccDir + 1 + len + 1;
            pAttributes->py_file[i] = (wchar_t *)malloc(count * sizeof(wchar_t));
            memcpy(pAttributes->py_file[i], tccDirectory, lenTccDir * sizeof(wchar_t));
            pAttributes->py_file[i][lenTccDir] = L'\\';
            memcpy(pAttributes->py_file[i] + lenTccDir + 1, tempString, (len + 1) * sizeof(wchar_t));
            free(tempString);
        }
    }
    free(rootDirectory);
}

int tcx_get_tcc_attributes(const wchar_t *tccFilename, TCC_pAttributes pAttributes) {
    int count;
    wchar_t *tccString;
    wchar_t *tccDirectory;
    if (libtcc_read_file_to_unicode(tccFilename, &tccString, &count) != tcc_error_success) {
        printf("Fatal Error: failed to read the TCC file.\n");
        return -1;
    }
    if (libtcc_check_format(tccString, count) != tcc_error_success) {
        free(tccString);
        printf("Fatal Error: bad formated TCC file.\n");
        return -1;
    }
    if (libtcc_parse_tcc_string(tccString, count, pAttributes) != tcc_error_success) {
        free(tccString);
        printf("Fatal Error: uncompleted TCC file.\n");
        return -1;
    }
    free(tccString);
    /* ensure the integrity of TCC attributes */
    tcx_get_tcc_directory(tccFilename, &tccDirectory);
    _tcx_make_full_path_of_syl_file(tccDirectory, pAttributes);
    _tcx_make_full_path_of_font_file(tccDirectory, pAttributes);
    _tcx_make_full_path_of_py_file(tccDirectory, pAttributes);
    free(tccDirectory);
    return 0;
}

int tcx_get_syl_line_and_word(const wchar_t *sylFilename, SYL_pLine pLine, SYL_pWord pWord) {
    int i, j, count;
    wchar_t *sylString;
    if (L'\0' == sylFilename[0])
        libsyl_create_simplest_syl_string(&sylString, &count);
    else {
        if (libsyl_read_file_to_unicode(sylFilename, &sylString, &count) != syl_error_success) {
            printf("Fatal Error: failed to read the SYL file.\n");
            return -1;
        }
    }
    if (libsyl_parse_syl_string(sylString, count, pLine) != syl_error_success) {
        free(sylString);
        printf("Fatal Error: bad formated SYL file.\n");
        return -1;
    }
    free(sylString);
    if (libsyl_check_line_format(pLine, &i, &j) != syl_error_success) {
        printf("Fatal Error: invalid SYL line, Line index = %i ; K index = %i\n", i, j);
        return -1;
    }
    libsyl_add_k0_to_white_spaces(pLine);
    libsyl_parse_line(pLine, pWord);
    return 0;
}

int tcx_init_tcxpy_data(const wchar_t *tccFilename, const wchar_t *outFilename, TCC_pAttributes pAttributes, PY_pInitData pInitData) {
    int count;
    SYL_Line sylLine;
    SYL_Word sylWord;
    SYL_Syllable sylSyllable;
    TM_Font tmFont;
    TM_TextHori tmTextHori;
    TM_TextVert tmTextVert;
    if (tcx_get_tcc_attributes(tccFilename, pAttributes) != 0) return -1;
    if (tcx_get_syl_line_and_word(pAttributes->k_timed_ass_file, &sylLine, &sylWord) != 0) {
        libtcc_free_attributes(pAttributes);
        return -1;
    }
    libsyl_parse_word_to_syllable(&sylWord, &sylSyllable);
    pAttributes->font_face_id -= 1;    /* in TCC file font_face_id starts from 1, in FreeType it starts from 0 */
    if (libtm_init_font(&tmFont, pAttributes->font_file, pAttributes->font_face_id, pAttributes->font_size, pAttributes->space_scale) != tm_error_success) {
        libtcc_free_attributes(pAttributes);
        libsyl_free_line(&sylLine);
        libsyl_free_word(&sylWord);
        libsyl_free_syllable(&sylSyllable);
        printf("Fatal Error: failed to initialize the font.\n");
        return -1;
    }
    if (L'\0' == pAttributes->font_face_name[0]) {
        free(pAttributes->font_face_name);
        count = wcslen(tmFont.name) + 1;
        pAttributes->font_face_name = (wchar_t *)malloc(count * sizeof(wchar_t));
        memcpy(pAttributes->font_face_name, tmFont.name, count * sizeof(wchar_t));
    }
    if (libtm_fill_tm_text_hori(&tmFont, &sylSyllable, pAttributes->spacing, &tmTextHori) != tm_error_success) {
        libtcc_free_attributes(pAttributes);
        libsyl_free_line(&sylLine);
        libsyl_free_word(&sylWord);
        libsyl_free_syllable(&sylSyllable);
        libtm_free_font(&tmFont);
        printf("Fatal Error: invalid character in SYL file.\n");
        return -1;
    }
    if (libtm_fill_tm_text_vert(&tmFont, &sylSyllable, pAttributes->spacing, &tmTextVert) != tm_error_success) {
        libtcc_free_attributes(pAttributes);
        libsyl_free_line(&sylLine);
        libsyl_free_word(&sylWord);
        libsyl_free_syllable(&sylSyllable);
        libtm_free_font(&tmFont);
        libtm_free_tm_text_hori(&tmTextHori);
        printf("Fatal Error: invalid character in SYL file.\n");
        return -1;
    }
    libtcc_info_to_tcx(pAttributes, (TCC_pInfoToTcx)&pInitData->tccData);
    if (K_MODE_WORD == pAttributes->k_mode) {
        TM_WordHori tmWordHori;
        TM_WordVert tmWordVert;
        libsyl_info_to_tcx_with_word(&sylLine, &sylWord, (SYL_pInfoToTcx)&pInitData->sylData);
        libtm_fill_tm_word_hori(&tmFont, &sylWord, &tmTextHori, &tmWordHori);
        libtm_info_to_tcx_with_word_hori(&tmFont, &tmWordHori, (TM_pInfoToTcxHori)&pInitData->tmHoriData);
        libtm_free_tm_word_hori(&tmWordHori);
        libtm_fill_tm_word_vert(&tmFont, &sylWord, &tmTextVert, &tmWordVert);
        libtm_info_to_tcx_with_word_vert(&tmFont, &tmWordVert, (TM_pInfoToTcxVert)&pInitData->tmVertData);
        libtm_free_tm_word_vert(&tmWordVert);
    } else if (K_MODE_SYL == pAttributes->k_mode) {
        libsyl_info_to_tcx_with_syl(&sylLine, &sylSyllable, (SYL_pInfoToTcx)&pInitData->sylData);
        libtm_info_to_tcx_with_text_hori(&tmFont, &tmTextHori, (TM_pInfoToTcxHori)&pInitData->tmHoriData);
        libtm_info_to_tcx_with_text_vert(&tmFont, &tmTextVert, (TM_pInfoToTcxVert)&pInitData->tmVertData);
    } else if (K_MODE_ADV == pAttributes->k_mode) {
        SYL_Adv sylAdv;
        TM_AdvHori tmAdvHori;
        TM_AdvVert tmAdvVert;
        libsyl_parse_word_to_adv(&sylWord, &sylAdv);
        libsyl_info_to_tcx_with_adv(&sylLine, &sylAdv, (SYL_pInfoToTcx)&pInitData->sylData);
        libtm_fill_tm_adv_hori(&tmFont, &sylAdv, &tmTextHori, &tmAdvHori);
        libtm_info_to_tcx_with_adv_hori(&tmFont, &tmAdvHori, (TM_pInfoToTcxHori)&pInitData->tmHoriData);
        libtm_free_tm_adv_hori(&tmAdvHori);
        libtm_fill_tm_adv_vert(&tmFont, &sylAdv, &tmTextVert, &tmAdvVert);
        libtm_info_to_tcx_with_adv_vert(&tmFont, &tmAdvVert, (TM_pInfoToTcxVert)&pInitData->tmVertData);
        libtm_free_tm_adv_vert(&tmAdvVert);
        libsyl_free_adv(&sylAdv);
    }
    libsyl_free_word(&sylWord);
    libsyl_free_syllable(&sylSyllable);
    libtm_free_font(&tmFont);
    libtm_free_tm_text_hori(&tmTextHori);
    libtm_free_tm_text_vert(&tmTextVert);
    if (pAttributes->inherit_ass_header) {
        count = wcslen(sylLine.header) + 1;
        pInitData->assHeader = (wchar_t *)malloc(count * sizeof(wchar_t));
        memcpy(pInitData->assHeader, sylLine.header, count * sizeof(wchar_t));
    } else libtcc_make_ass_header(pAttributes, &pInitData->assHeader, &count);
    libsyl_free_line(&sylLine);
    tcx_get_root_directory(&pInitData->directory);
    count = wcslen(outFilename) + 1;
    pInitData->outFilename = (wchar_t *)malloc(count * sizeof(wchar_t));
    memcpy(pInitData->outFilename, outFilename, count * sizeof(wchar_t));
    return 0;
}

void tcx_free_tcxpy_data(PY_pInitData pInitData) {
    free(pInitData->assHeader);
    free(pInitData->directory);
    free(pInitData->outFilename);
    libtcc_free_info((TCC_pInfoToTcx)&pInitData->tccData);
    libsyl_free_info((SYL_pInfoToTcx)&pInitData->sylData);
    libtm_free_info_hori((TM_pInfoToTcxHori)&pInitData->tmHoriData);
    libtm_free_info_vert((TM_pInfoToTcxVert)&pInitData->tmVertData);
}

int tcx_write_ass_string_to_file(FILE *assfp, const wchar_t *assString, int count) {
    int size;
    char *utf8String;
    size = WideCharToMultiByte(CP_UTF8, 0, assString, count, NULL, 0, NULL, NULL);
    utf8String = (char *)malloc(size);
    WideCharToMultiByte(CP_UTF8, 0, assString, count, utf8String, size, NULL, NULL);
    if (fwrite(utf8String, sizeof(char), size, assfp) != size) {
        free(utf8String);
        return -1;
    }
    free(utf8String);
    return 0;
}

int tcx_write_tcs_buffer_to_file(TCS_pFile pFile, TCS_pHeader pHeader, const unsigned long *tcsBuffer, int count) {
    tcs_unit *buf;
    tcs_u32 chunks, units;
    libtcs_get_raw_chunks_min_max_time((const TCS_pRawChunk)tcsBuffer, count, pHeader);
    libtcs_compress_raw_chunks((const TCS_pRawChunk)tcsBuffer, count, &buf, &chunks, &units);
    pHeader->chunks += chunks;
    if (libtcs_write(pFile, buf, units) != tcs_error_success) {
        free(buf);
        return -1;
    }
    free(buf);
    return 0;
}

int tcx_get_line_init_pos_x(const TCC_pAttributes pAttributes, const PY_pInitData pInitData, int iLine) {
    int lineInitPosX;
    if (TEXT_LAYOUT_HORI == pAttributes->text_layout) {
        switch (pAttributes->alignment) {
        case 1:
        case 4:
        case 7:
            lineInitPosX = pAttributes->x_offset;
            break;
        case 2:
        case 5:
        case 8:
            lineInitPosX = (int)((pAttributes->fx_width - pInitData->tmHoriData.length1D[iLine]) / 2.0 + 0.5) + pAttributes->x_offset;
            break;
        case 3:
        case 6:
        case 9:
            lineInitPosX = pAttributes->fx_width - pInitData->tmHoriData.length1D[iLine] - pAttributes->x_offset;
            break;
        }
    } else {
        switch (pAttributes->alignment) {
        case 1:
        case 4:
        case 7:
            lineInitPosX = pAttributes->x_offset;
            break;
        case 2:
        case 5:
        case 8:
            lineInitPosX = (int)(pAttributes->fx_width / 2.0 + 0.5) + pAttributes->x_offset;
            break;
        case 3:
        case 6:
        case 9:
            lineInitPosX = pAttributes->fx_width - pAttributes->font_size - pAttributes->x_offset;
            break;
        }
    }
    return lineInitPosX;
}

int tcx_get_line_init_pos_y(const TCC_pAttributes pAttributes, const PY_pInitData pInitData, int iLine) {
    int lineInitPosY;
    if (TEXT_LAYOUT_HORI == pAttributes->text_layout) {
        switch (pAttributes->alignment) {
        case 1:
        case 2:
        case 3:
            lineInitPosY = pAttributes->fx_height - pAttributes->y_offset - pAttributes->font_size;
            break;
        case 4:
        case 5:
        case 6:
            lineInitPosY = (int)(pAttributes->fx_height / 2.0 + 0.5) - pAttributes->y_offset;
            break;
        case 7:
        case 8:
        case 9:
            lineInitPosY = pAttributes->y_offset;
            break;
        }
    } else {
        switch (pAttributes->alignment) {
        case 1:
        case 2:
        case 3:
            lineInitPosY = pAttributes->fx_height - pInitData->tmVertData.length1D[iLine] - pAttributes->y_offset;
            break;
        case 4:
        case 5:
        case 6:
            lineInitPosY = (int)((pAttributes->fx_height - pInitData->tmVertData.length1D[iLine]) / 2.0 + 0.5) - pAttributes->y_offset;
            break;
        case 7:
        case 8:
        case 9:
            lineInitPosY = pAttributes->y_offset;
            break;
        }
    }
    return lineInitPosY;
}

static void _tcx_get_numerator_and_denominator(double fps, unsigned long *pNumerator, unsigned long *pDenominator) {
    unsigned long numerator, denominator, x, y, t;
    denominator = 100000;
    numerator   = (unsigned long)(fps * denominator);
    x = numerator;
    y = denominator;
    while (y) {   // find gcd
        t = x % y;
        x = y;
        y = t;
    }
    numerator /= x;
    denominator /= x;
    *pNumerator = numerator;
    *pDenominator = denominator;
}

static int _tcx_is_text_c_or_j_or_k(const wchar_t *text) {
    int i, count;
    count = wcslen(text);
    for (i = 0; i < count; i ++) {
        if (text[0] >= 0x3000 && text[1] <= 0x9FFF) return 1;
    }
    return 0;
}

static void _tcx_adjust_vertical_metrics(PY_pInitData pInitData) {
    int i, j, k, index;
    int *pKerning;
    pKerning = (int *)malloc(pInitData->sylData.count * sizeof(int));
    memset(pKerning, 0, pInitData->sylData.count * sizeof(int));
    k = 0;
    for (i = 0; i < pInitData->sylData.count; i ++) {
        if (!_tcx_is_text_c_or_j_or_k(pInitData->sylData.text2D[i])) {
            pInitData->tmVertData.advance2D[i] = pInitData->tmHoriData.advance2D[i];
            if (i - 1 == k)    /* if the two horizontal ones are close to each other then they should have kerning */
                pKerning[i] = pInitData->tmHoriData.kerning2D[i];
            k = i;
        }
    }
    index = 0;
    for (i = 0; i < pInitData->sylData.lines; i ++) {
        k = index;
        pInitData->tmVertData.advanceDiff2D[k] = 0;
        index ++;
        for (j = 1; j < pInitData->sylData.count1D[i]; j ++) {
            pInitData->tmVertData.advanceDiff2D[index] = pInitData->tmVertData.advanceDiff2D[index - 1] + pInitData->tmVertData.advance2D[index - 1] + pKerning[index] + pInitData->tccData.spacing;
            index ++;
        }
    }
    free(pKerning);
}

static int _tcx_use_user_func(const PY_pTcxPy pTcxPy, const PY_pInitData pInitData, const TCC_pAttributes pAttributes) {
    int i;
    for (i = 0; i < pAttributes->py_file_count; i ++) {
        if (pAttributes->beg_line[i] <= 0)
            pAttributes->beg_line[i] += pInitData->sylData.lines;
        pAttributes->beg_line[i] -= 1;    /* in TCC file beg_line starts from 1, in tcx it starts from 0 */
        if (pAttributes->end_line[i] <= 0)
            pAttributes->end_line[i] += pInitData->sylData.lines;
        if (pAttributes->beg_line[i] >= pAttributes->end_line[i])
            printf("Warning: the %i(th) `beg line' is greater than `end line'\n", i + 1);
    }
    for (i = 0; i < pAttributes->py_file_count; i ++) {
        if (tcxpy_init_user_py_module(pTcxPy, pInitData->directory, pAttributes->py_file[i], pAttributes->tcxpy_init, pAttributes->tcxpy_user, pAttributes->tcxpy_fin) != py_error_success) {
            printf("Fatal Error: failed to initialize the %i(th) user tcx py script.\n", i + 1);
            return -1;
        }
        if (pAttributes->tcxpy_init) {
            if (tcxpy_script_func_init(pTcxPy) != py_error_success) {
                printf("Fatal Error: failed to execute the tcxPy_Init() function in the %i(th) user tcx py script.\n", i + 1);
                return -1;
            }
        }
        if (tcxpy_script_func_user(pTcxPy) != py_error_success) {
            printf("Fatal Error: failed to execute the tcxPy_User() function in the %i(th) user tcx py script.\n", i + 1);
            return -1;
        }
        if (pAttributes->tcxpy_fin) {
            if (tcxpy_script_func_fin(pTcxPy) != py_error_success) {
                printf("Fatal Error: failed to execute the tcxPy_Fin() function in the %i(th) user tcx py script.\n", i + 1);
                return -1;
            }
        }
    }
    return 0;
}

static int _tcx_use_main_func(const PY_pTcxPy pTcxPy, const PY_pInitData pInitData, const TCC_pAttributes pAttributes) {
    int i;
    unsigned char BOM[3];
    wchar_t *assFilename;
    char *tcsFilename;
    FILE *assfp;
    TCS_File tcsFile;
    TCS_Header tcsHeader;
    /* Show progress */
    HANDLE hStdout;
    COORD coord;
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleScreenBufferInfo(hStdout, &csbi);
    coord.X = 0;
    coord.Y = csbi.dwCursorPosition.Y + 1;
    /* */
    assfp = _wfopen(tcx_get_out_ass_filename(pInitData->outFilename, &assFilename), L"wb");
    if (!assfp) {
        free(assFilename);
        printf("Fatal Error: failed to create the output ASS file.\n");
        return -1;
    }
    free(assFilename);
    if (libtcs_open_file(&tcsFile, tcx_get_out_tcs_filename(pInitData->outFilename, &tcsFilename), tcs_file_create_new) != tcs_error_success) {
        free(tcsFilename);
        fclose(assfp);
        printf("Fatal Error: failed to create the output TCS file.\n");
        return -1;
    }
    free(tcsFilename);
    BOM[0] = 0xEF;
    BOM[1] = 0xBB;
    BOM[2] = 0xBF;
    fwrite(BOM, sizeof(unsigned char), 3, assfp);
    tcx_write_ass_string_to_file(assfp, pInitData->assHeader, wcslen(pInitData->assHeader));
    _tcx_get_numerator_and_denominator(pAttributes->fx_fps, &tcsHeader.fpsNumerator, &tcsHeader.fpsDenominator);
    libtcs_set_header(&tcsHeader, TCS_FLAG_COMPRESSED, pAttributes->fx_width, pAttributes->fx_height, TCS_INIT_MIN_TIME, TCS_INIT_MAX_TIME, 0, tcsHeader.fpsNumerator, tcsHeader.fpsDenominator);
    libtcs_set_file_position_indicator(&tcsFile, tcs_fpi_header);
    for (i = 0; i < pAttributes->py_file_count; i ++) {
        if (pAttributes->beg_line[i] <= 0)
            pAttributes->beg_line[i] += pInitData->sylData.lines;
        pAttributes->beg_line[i] -= 1;    /* in TCC file beg_line starts from 1, in tcx it starts from 0 */
        if (pAttributes->end_line[i] <= 0)
            pAttributes->end_line[i] += pInitData->sylData.lines;
        if (pAttributes->beg_line[i] >= pAttributes->end_line[i])
            printf("Warning: the %i(th) `beg line' is greater than `end line'\n", i + 1);
    }
    for (i = 0; i < pAttributes->py_file_count; i ++) {
        int index, offset, total;
        int iLine, iText, nTexts, start, end, timeDiff, time, ix, iy, x, y, advance;
        const wchar_t *text;
        void **pReturnedBuf;
        if (tcxpy_init_user_py_module(pTcxPy, pInitData->directory, pAttributes->py_file[i], pAttributes->tcxpy_init, pAttributes->tcxpy_user, pAttributes->tcxpy_fin) != py_error_success) {
            fclose(assfp);
            libtcs_close_file(&tcsFile);
            printf("Fatal Error: failed to initialize the %i(th) user tcx py script.\n", i + 1);
            return -1;
        }
        if (pAttributes->tcxpy_init) {
            if (tcxpy_script_func_init(pTcxPy) != py_error_success) {
                fclose(assfp);
                libtcs_close_file(&tcsFile);
                printf("Fatal Error: failed to execute the tcxPy_Init() function in the %i(th) user tcx py script.\n", i + 1);
                return -1;
            }
        }
        if (0 == pAttributes->beg_line[i] && pInitData->sylData.lines == pAttributes->end_line[i]) {
            offset = 0;
            total = pInitData->sylData.count;
        } else {
            offset = 0;
            for (iLine = 0; iLine < pAttributes->beg_line[i]; iLine ++)
                offset += pInitData->sylData.count1D[iLine];
            total = 0;
            for (iLine = pAttributes->beg_line[i]; iLine < pAttributes->end_line[i]; iLine ++)
                total += pInitData->sylData.count1D[iLine];
        }
        index = 0;
        for (iLine = 0; iLine < pInitData->sylData.lines; iLine ++) {
            ix = tcx_get_line_init_pos_x(pAttributes, pInitData, iLine);
            iy = tcx_get_line_init_pos_y(pAttributes, pInitData, iLine);
            nTexts = pInitData->sylData.count1D[iLine];
            for (iText = 0; iText < nTexts; iText ++) {
                text = pInitData->sylData.text2D[index];
                if (iLine >= pAttributes->beg_line[i] && iLine < pAttributes->end_line[i] && !libsyl_is_white_spaces(text, wcslen(text))) {
                    start = pInitData->sylData.begTime1D[iLine];
                    end = pInitData->sylData.endTime1D[iLine];
                    timeDiff = pInitData->sylData.timeDiff2D[index];
                    time = pInitData->sylData.time2D[index];
                    if (TEXT_LAYOUT_HORI == pAttributes->text_layout) {
                        advance = pInitData->tmHoriData.advance2D[index];
                        x = ix + pInitData->tmHoriData.advanceDiff2D[index] + (int)(advance / 2.0 + 0.5);
                        y = iy + (int)(pAttributes->font_size / 2.0 + 0.5);
                    } else {
                        advance = pInitData->tmVertData.advance2D[index];
                        x = ix + (int)(pAttributes->font_size / 2.0 + 0.5);
                        y = iy + pInitData->tmVertData.advanceDiff2D[index] + (int)(advance / 2.0 + 0.5);
                    }
                    pReturnedBuf = tcxpy_script_func_main(pTcxPy, iLine, iText, nTexts, start, end, timeDiff, time, x, y, advance, text);
                    if (!pReturnedBuf) {
                        fclose(assfp);
                        libtcs_write_header(&tcsFile, &tcsHeader, 0);
                        libtcs_close_file(&tcsFile);
                        printf("Fatal Error: failed to execute the tcxPy_Main() function in the %i(th) user tcx py script.\n", i + 1);
                        return -1;
                    }
                    if ((int)pReturnedBuf[0] > 0) {
                        tcx_write_ass_string_to_file(assfp, (const wchar_t *)pReturnedBuf[1], (int)pReturnedBuf[0]);
                        free(pReturnedBuf[1]);
                    }
                    if ((int)pReturnedBuf[2] > 0) {
                        tcx_write_tcs_buffer_to_file(&tcsFile, &tcsHeader, (const unsigned long *)pReturnedBuf[3], (int)pReturnedBuf[2]);
                        free(pReturnedBuf[3]);
                    }
                    free(pReturnedBuf);
                    SetConsoleCursorPosition(hStdout, coord);
                    printf("Progress: %.2f%c\n", 100 * (i + (index - offset) / (double)total) / pAttributes->py_file_count, '%');
                }
                index ++;
            }
        }
        if (pAttributes->tcxpy_fin) {
            if (tcxpy_script_func_fin(pTcxPy) != py_error_success) {
                fclose(assfp);
                libtcs_close_file(&tcsFile);
                printf("Fatal Error: failed to execute the tcxPy_Fin() function in the %i(th) user tcx py script.\n", i + 1);
                return -1;
            }
        }
    }
    fclose(assfp);
    libtcs_write_header(&tcsFile, &tcsHeader, 0);
    libtcs_close_file(&tcsFile);
    SetConsoleCursorPosition(hStdout, coord);
    printf("Progress: 100.00%c\n", '%');
    return 0;
}

int tcx_entry(const wchar_t *tccFilename, const wchar_t *outFilename) {
    TCC_Attributes tccAttributes;
    PY_InitData InitData;
    PY_TcxPy TcxPy;
    memset(&TcxPy, 0, sizeof(PY_TcxPy));
    if (tcx_init_tcxpy_data(tccFilename, outFilename, &tccAttributes, &InitData) != 0) return -1;
    _tcx_adjust_vertical_metrics(&InitData);
    if (tcxpy_init_base_py_module(&TcxPy, &InitData) != py_error_success) {
        libtcc_free_attributes(&tccAttributes);
        tcx_free_tcxpy_data(&InitData);
        printf("Fatal Error: failed to initialize the base tcxPy module.\n");
        return -1;
    }
    if (tccAttributes.tcxpy_user) {
        if (_tcx_use_user_func(&TcxPy, &InitData, &tccAttributes) != 0) {
            libtcc_free_attributes(&tccAttributes);
            tcx_free_tcxpy_data(&InitData);
            tcxpy_fin_tcxpy(&TcxPy);
            return -1;
        }
    } else {
        if (_tcx_use_main_func(&TcxPy, &InitData, &tccAttributes) != 0) {
            libtcc_free_attributes(&tccAttributes);
            tcx_free_tcxpy_data(&InitData);
            tcxpy_fin_tcxpy(&TcxPy);
            return -1;
        }
    }
    libtcc_free_attributes(&tccAttributes);
    tcx_free_tcxpy_data(&InitData);
    tcxpy_fin_tcxpy(&TcxPy);
    return 0;
}

